#include "cmap_parser.h"

namespace {
uint16_t Revert16(uint16_t num)
{
    return ((num & 0x00ff) << 8) | ((num & 0xff00) >> 8);
}

uint32_t Revert32(uint32_t num)
{
    return ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) |
        ((num & 0x0000ff00) << 8) | ((num & 0x000000ff) << 24);
}

struct EncodingRecord {
    uint16_t platformID;
    uint16_t encodingID;
    uint32_t subtableOffset;

    uint16_t GetPlatformID() const { return Revert16(platformID); }
    uint16_t GetEncodingID() const { return Revert16(encodingID); }
    uint32_t GetSubtableOffset() const { return Revert32(subtableOffset); }

    uint16_t GetFormat(const void *data) const
    {
        return Revert16(*reinterpret_cast<const uint16_t *>(
            reinterpret_cast<const char *>(data) + GetSubtableOffset()));
    }
};

struct CmapTable {
    uint16_t version;
    uint16_t numTables;
    struct EncodingRecord encodingRecords[];

    uint16_t GetNumTables() const { return Revert16(numTables); }
};

struct Uint16Array {
    uint16_t array[];
    uint16_t Get(int index) const { return Revert16(array[index]); }
};

struct SubtableFormat4 {
    uint16_t format;
    uint16_t length;
    uint16_t language;
    uint16_t segCountX2;
    uint16_t searchRange;
    uint16_t entrySelector;
    uint16_t rangeShift;
    uint16_t array[];

    uint16_t GetFormat() const { return Revert16(format); }
    uint16_t GetLength() const { return Revert16(length); }
    uint16_t GetLanguage() const { return Revert16(language); }
    uint16_t GetSegCount() const { return Revert16(segCountX2) / 2; }
    uint16_t GetSearchRange() const { return Revert16(searchRange); }
    uint16_t GetEntrySelector() const { return Revert16(entrySelector); }
    uint16_t GetRangeShift() const { return Revert16(rangeShift); }

    const struct Uint16Array *GetEndCode() const
    {
        return reinterpret_cast<const struct Uint16Array *>(array);
    }

    const struct Uint16Array *GetStartCode() const
    {
        return reinterpret_cast<const struct Uint16Array *>(array + GetSegCount() + 1);
    }

    const struct Uint16Array *GetIdDelta() const
    {
        return reinterpret_cast<const struct Uint16Array *>(array + GetSegCount() * 2 + 1);
    }

    const struct Uint16Array *GetGlyphIdArray() const
    {
        return reinterpret_cast<const struct Uint16Array *>(array + GetSegCount() * 3 + 1);
    }
};
} // namespace

CmapParser::CmapParser(const void *data, int32_t size)
    : data_(data), size_(size)
{
}

int CmapParser::Parse(Ranges &ranges)
{
    auto cmap = reinterpret_cast<const struct CmapTable *>(data_);
    for (auto i = 0; i < cmap->GetNumTables(); i++) {
        auto &record = cmap->encodingRecords[i];
        if (record.GetFormat(data_) == 4) {
            const char *data = reinterpret_cast<const char *>(data_);
            auto offset = record.GetSubtableOffset();
            if (!ParseFormat4(data + offset, size_ - offset, ranges)) {
                return 0;
            }
        }
    }
    return 1;
}

int CmapParser::ParseFormat4(const char *data, const std::size_t size, Ranges &ranges)
{
    auto subtable = reinterpret_cast<const SubtableFormat4 *>(data);
    auto endCode = subtable->GetEndCode();
    auto startCode = subtable->GetStartCode();
    auto idDelta = subtable->GetIdDelta();
    auto glyphIdArray = subtable->GetGlyphIdArray();

    if (size < sizeof(SubtableFormat4)) {
        return 1;
    }

    if (size < sizeof(SubtableFormat4) + sizeof(uint16_t) * (subtable->GetSegCount() * 4 + 1)) {
        return 1;
    }

    for (uint32_t i = 0; i < subtable->GetSegCount(); i++) {
        uint32_t end = endCode->Get(i);
        uint32_t start = startCode->Get(i);
        if (end < start) {
            return 1;
        }

        uint32_t glyphId = glyphIdArray->Get(i);
        if (glyphId == 0) {
            uint32_t delta = idDelta->Get(i);
            if (((end + delta) & 0xffff) > end - start) {
                ranges.AddRange(start, end + 1);
            } else {
                for (uint32_t j = start; j <= end; j++) {
                    if ((j + delta) & 0xffff) {
                        ranges.AddRange(j, j + 1);
                    }
                }
            }
        } else {
            for (uint32_t j = start; j <= end; j++) {
                if (glyphIdArray->Get(glyphId / 2 + i + j - start)) {
                    ranges.AddRange(j, j + 1);
                }
            }
        }
    }
    return 0;
}
